home *** CD-ROM | disk | FTP | other *** search
/ Tricks of the Mac Game Programming Gurus / TricksOfTheMacGameProgrammingGurus.iso / More Source / C⁄C++ / Pop Up Menu CDEF / Pop Up.c < prev    next >
Text File  |  1995-06-11  |  15KB  |  467 lines

  1. // Pop Up menu CDEF
  2. // (C) 1990-1995 Stuart Cheshire <cheshire@cs.stanford.edu>
  3. // Written after endless frustration with the one by Chris Faigle which
  4. // used to crash all the time, to whom some credit for inspiration should go.
  5.  
  6. // Note: The MacTraps library is required, but only for GetHandleSize
  7. // Hopefully the linker should be smart enough not to include all of MacTraps
  8.  
  9. #include <Traps.h>
  10.  
  11. #define FIXED_MENU_WIDTH            1
  12. #define    NEW_STYLE                    2
  13. #define USE_ADD_RES_MENU            4
  14. #define RIGHT_JUSTIFY_TITLE            8
  15. #define OVERRIDE_MENU_SIZE            (FIXED_MENU_WIDTH | NEW_STYLE)
  16.  
  17. #define    BOTTOM_SPACE                5
  18. #define    LEFT_SPACE                    13
  19. #define    INACTIVE                    255
  20. #define    EXTRA_SPACE_FOR_NEW_STYLE    13
  21. #define NULL_MENU_WIDTH                60
  22.  
  23. #define    SICN_SPACE                    20
  24. #define    RICN_SPACE                    20
  25. #define    ICON_SPACE                    36
  26.  
  27. #define    RICN_MENU                    29
  28. #define    SICN_MENU                    30
  29.  
  30. #define    NORMAL_MENU_SIZE            16
  31. #define    RICN_MENU_SIZE                20
  32. #define    SICN_MENU_SIZE                20
  33. #define    ICON_MENU_SIZE                36
  34.  
  35. typedef    short        SICN[16];
  36. typedef    SICN        *SICNList;
  37. typedef    SICNList    *SICNHand;
  38.  
  39. typedef struct
  40.     {
  41.     MenuHandle menu;            // Handle to the actal menu in memory
  42.     short menuID;                // Id of the menu
  43.     short resID;                // resource id of menu to use, or zero if none
  44.     short width, font, size;    // width, font and size to draw the title in
  45.     } pop_up_data;
  46.  
  47. #define POPDATA(C) ((pop_up_data *)((C)[0]->contrlData[0]))
  48.  
  49. #ifndef    calcCntlRgn
  50. #define    calcCntlRgn                    10
  51. #endif
  52.  
  53. #ifndef    calcThumbRgn
  54. #define    calcThumbRgn                11
  55. #endif
  56.  
  57. #ifndef NULL
  58. #define    NULL                        0L
  59. #endif
  60.  
  61. static MenuHandle get_pop_menu(ControlHandle cntrl)
  62.     {
  63.     if (POPDATA(cntrl)->resID) return(POPDATA(cntrl)->menu);
  64.     else return((MenuHandle)cntrl[0]->contrlRfCon);
  65.     }
  66.  
  67. // If the control is untitled, then calc_rects returns title_rect as an empty rectangle
  68.  
  69. static void calc_rects(ControlHandle cntrl, short ctype, Rect *title_rect, Rect *menu_rect)
  70.     {
  71.     Rect        *control_rect = &cntrl[0]->contrlRect;
  72.     MenuHandle    the_menu      = get_pop_menu(cntrl);
  73.     short        menu_height   = NORMAL_MENU_SIZE;
  74.     
  75.     if (the_menu)
  76.         {
  77.         CalcMenuSize(the_menu);
  78.         // If valid menu selection, work out the menu item height
  79.         if (cntrl[0]->contrlValue >= 1 &&
  80.             cntrl[0]->contrlValue <= CountMItems(the_menu))
  81.             {
  82.             short command_char;
  83.             GetItemCmd(the_menu, cntrl[0]->contrlValue, &command_char);
  84.             switch(command_char)
  85.                 {
  86.                 short icon;
  87.                 case SICN_MENU:    menu_height = SICN_MENU_SIZE; break;
  88.                 case RICN_MENU:    menu_height = RICN_MENU_SIZE; break;
  89.                 default:        GetItemIcon(the_menu, cntrl[0]->contrlValue, &icon);
  90.                                 if (icon) menu_height = ICON_MENU_SIZE;
  91.                                 break;
  92.                 }
  93.             }
  94.         }
  95.  
  96.     // Work out top, left and bottom of menu rectangle and title rectangle
  97.     menu_rect->left   = control_rect->left;
  98.     menu_rect->top    = (control_rect->top + control_rect->bottom - menu_height)>>1;
  99.     menu_rect->bottom = menu_rect->top + menu_height;
  100.  
  101.     title_rect->top    = (control_rect->top+control_rect->bottom)/2-8;
  102.     title_rect->left   = control_rect->left;
  103.     title_rect->bottom = title_rect->top+16;
  104.     title_rect->right  = control_rect->left;
  105.  
  106.     if (cntrl[0]->contrlTitle[0])        // If this control has a title
  107.         {
  108.         TextFont(POPDATA(cntrl)->font);
  109.         TextSize(POPDATA(cntrl)->size);
  110.         TextFace(0);
  111.         if (POPDATA(cntrl)->width)
  112.              title_rect->right += POPDATA(cntrl)->width;
  113.         else title_rect->right += StringWidth(cntrl[0]->contrlTitle)+1;
  114.         menu_rect->left    = title_rect->right+3;
  115.         }
  116.  
  117.     if (ctype & FIXED_MENU_WIDTH) menu_rect->right = control_rect->right;
  118.     else
  119.         {
  120.         if (the_menu && the_menu[0] && the_menu[0]->menuWidth > 2)
  121.              menu_rect->right=menu_rect->left+the_menu[0]->menuWidth;
  122.         else menu_rect->right=menu_rect->left+NULL_MENU_WIDTH;
  123.         
  124.         if (ctype & NEW_STYLE) menu_rect->right += EXTRA_SPACE_FOR_NEW_STYLE;
  125.         }
  126.     }
  127.  
  128. static void draw_menu_content(ControlHandle cntrl, MenuHandle the_menu, Rect menu_rect)
  129.     {
  130.     Str255 current_selection_text;
  131.     short command_char, which_icon, text_x;
  132.     Rect icon_rect;
  133.     Handle icon_handle;
  134.     FontInfo font_info;
  135.     Style text_style;
  136.  
  137.     TextFont(systemFont);
  138.     TextSize(0);
  139.     TextFace(0);
  140.     GetFontInfo(&font_info);        // Get System font information to draw menu text
  141.  
  142.     // If control value is out of range for the menu,
  143.     // display the menu's title instead
  144.     if (cntrl[0]->contrlValue < 1 ||
  145.         cntrl[0]->contrlValue > CountMItems(the_menu))
  146.         {
  147.         BlockMoveData(the_menu[0]->menuData, current_selection_text,
  148.                     1+the_menu[0]->menuData[0]);
  149.         text_x = menu_rect.left+LEFT_SPACE+2;
  150.         }
  151.     else
  152.         {
  153.         GetItem(the_menu,cntrl[0]->contrlValue,current_selection_text);
  154.         GetItemCmd(the_menu,cntrl[0]->contrlValue,&command_char);
  155.         GetItemStyle(the_menu, cntrl[0]->contrlValue, &text_style);
  156.         TextFace(text_style);
  157.         switch(command_char)
  158.             {
  159.             case SICN_MENU:
  160.                 {
  161.                 short sicn;
  162.                 BitMap        src_bits = { 0, 2, {0,0,16,16}};
  163.                 SICNHand    sicn_hand;
  164.                 icon_rect.top    = menu_rect.top+3,
  165.                 icon_rect.left   = menu_rect.left+LEFT_SPACE;
  166.                 icon_rect.bottom = menu_rect.top+3+16;
  167.                 icon_rect.right  = menu_rect.left+LEFT_SPACE+2+16;
  168.                 GetItemIcon(the_menu, cntrl[0]->contrlValue, &sicn);
  169.                 if (sicn_hand = (SICNHand)GetResource('SICN', sicn+256))
  170.                     {
  171.                     HLock((Handle)sicn_hand);
  172.                     src_bits.baseAddr = (Ptr)*sicn_hand;
  173.                     if(GetHandleSize((Handle)sicn_hand) >= sizeof(SICN))
  174.                         CopyBits(&src_bits, &cntrl[0]->contrlOwner->portBits,
  175.                                 &src_bits.bounds,&icon_rect,srcCopy,NULL);
  176.                     HUnlock((Handle)sicn_hand);
  177.                     }
  178.                 text_x = menu_rect.left+LEFT_SPACE+SICN_SPACE;
  179.                 break;
  180.                 }
  181.     
  182.             case RICN_MENU:
  183.                 GetItemIcon(the_menu,cntrl[0]->contrlValue,&which_icon);
  184.                 icon_handle=GetResource('ICON',which_icon+256);
  185.                 if(icon_handle)
  186.                     {
  187.                     icon_rect.left   = menu_rect.left+LEFT_SPACE+2;
  188.                     icon_rect.right  = icon_rect.left+16;
  189.                     icon_rect.top    = (menu_rect.bottom+menu_rect.top-16)/2;
  190.                     icon_rect.bottom = icon_rect.top+16;
  191.                     PlotIcon(&icon_rect,icon_handle);
  192.                     }
  193.                 text_x = menu_rect.left+LEFT_SPACE+RICN_SPACE;
  194.                 break;
  195.     
  196.             default:
  197.                 GetItemIcon(the_menu,cntrl[0]->contrlValue,&which_icon);
  198.                 if (which_icon==0) text_x = menu_rect.left+LEFT_SPACE+2;
  199.                 else
  200.                     {
  201.                     icon_handle=GetResource('ICON',which_icon+256);
  202.                     if(icon_handle!=NULL)
  203.                         {
  204.                         icon_rect.left   = menu_rect.left + LEFT_SPACE+2;
  205.                         icon_rect.right  = icon_rect.left + 32;
  206.                         icon_rect.top    = (menu_rect.bottom + menu_rect.top-32)/2;
  207.                         icon_rect.bottom = icon_rect.top+32;
  208.                         PlotIcon(&icon_rect,icon_handle);
  209.                         }
  210.                     text_x = menu_rect.left+LEFT_SPACE+ICON_SPACE;
  211.                     }
  212.                 break;
  213.             }
  214.         }
  215.  
  216.     MoveTo(text_x,(menu_rect.top+menu_rect.bottom+font_info.ascent-font_info.descent)/2);
  217.     DrawString(current_selection_text);
  218.     }
  219.  
  220. static void draw(short ctype, ControlHandle cntrl)
  221.     {
  222.     Rect title_rect, menu_rect;
  223.     FontInfo font_info;
  224.     MenuHandle the_menu = get_pop_menu(cntrl);
  225.  
  226.     PenNormal();
  227.     calc_rects(cntrl, ctype, &title_rect, &menu_rect);
  228.     GetFontInfo(&font_info); // calc_rects sets the current font to the title font
  229.  
  230.     // Draw the title
  231.     if (cntrl[0]->contrlTitle[0])
  232.         {
  233.         short v = (title_rect.top+title_rect.bottom+font_info.ascent-font_info.descent)/2;
  234.         if (!(ctype & RIGHT_JUSTIFY_TITLE)) MoveTo(title_rect.left+1, v);
  235.         else MoveTo(title_rect.right - StringWidth(cntrl[0]->contrlTitle), v);
  236.         DrawString(cntrl[0]->contrlTitle);
  237.         }
  238.  
  239.     // Draw the menu content region
  240.     EraseRect(&menu_rect);            // Erase content region of control
  241.     InsetRect(&menu_rect,-1,-1);    // Enlarge control by one pixel
  242.     FrameRect(&menu_rect);            // Frame just outside the erased region
  243.  
  244.     // If active control, draw drop shadow, else erase drop shadow
  245.     if(cntrl[0]->contrlHilite == INACTIVE) PenMode(patBic);
  246.     MoveTo(menu_rect.right,menu_rect.top+2);
  247.     LineTo(menu_rect.right,menu_rect.bottom);
  248.     LineTo(menu_rect.left+2,menu_rect.bottom);
  249.     if(cntrl[0]->contrlHilite == INACTIVE) PenMode(patCopy);
  250.     
  251.     // If new style, draw the little arrow
  252.     if (ctype & NEW_STYLE)
  253.         {
  254.         short i, h = menu_rect.right-10, v = (menu_rect.bottom+menu_rect.top)/2-3;
  255.         for (i=0;i<6;++i) { MoveTo(h+i-6,v+i); Line(10-(i*2),0); }
  256.         }
  257.     
  258.     if (the_menu) draw_menu_content(cntrl, the_menu, menu_rect);
  259.  
  260.     if(cntrl[0]->contrlHilite == INACTIVE)
  261.         {
  262.         long dimpat[2];                // WARNING! cannot use ANY globals
  263.         dimpat[0] = 0xAA55AA55;
  264.         dimpat[1] = 0xAA55AA55;
  265.         PenPat((ConstPatternParam)dimpat);
  266.         PenMode(patBic);
  267.         if (cntrl[0]->contrlTitle[0]) PaintRect(&title_rect);
  268.         InsetRect(&menu_rect,1,1);
  269.         PaintRect(&menu_rect);
  270.         }
  271.     }
  272.  
  273. // ************************************************************************
  274.  
  275. // Yuk! PopUpMenuSelect calls CalcMenuSize, so we have to patch CalcMenuSize
  276. // to stop it resetting the menu size to the original value
  277.  
  278. long _realwidth(void);
  279. typedef struct { unsigned short opcode; unsigned long menuSize; } movei;
  280.  
  281. static pascal void CalcMenuSize_patch(MenuHandle m)
  282.     {
  283.     asm    {
  284.         move.l    m, a0
  285.         move.l    (a0), a0
  286.     extern _realwidth:
  287.         move.l    #0x12345678, MenuInfo.menuWidth(a0)
  288.         }
  289.     }
  290.  
  291. static Boolean TrapAvailable(unsigned long trap)
  292.     {
  293.     TrapType tType = (trap & 0x800 ? ToolTrap : OSTrap);
  294.     if (trap & 0x800)                // if it is a ToolBox Trap
  295.         {
  296.         unsigned long n = 0x400;    // number of toolbox traps
  297.         if (NGetTrapAddress(_InitGraf, ToolTrap) == NGetTrapAddress(0xAA6E, ToolTrap))
  298.             n = 0x200;
  299.         if ((trap &= 0x7FF) >= n) trap = _Unimplemented;
  300.         }
  301.     return(NGetTrapAddress(trap, tType) != NGetTrapAddress(_Unimplemented, ToolTrap));
  302.     }
  303.  
  304. // drag returns 1 if a menu item is selected (even if it is the same as
  305. // the already selected item)
  306. static long drag(short ctype, ControlHandle cntrl)
  307.     {
  308.     long        retval = 0;
  309.     Point        point;
  310.     short        old_choice;
  311.     long        chosen;
  312.     MenuHandle    the_menu = get_pop_menu(cntrl);
  313.     Rect        title_rect, menu_rect;
  314.     short        original_menuwidth;
  315.     unsigned long *menuSize = &((movei*)_realwidth)->menuSize;
  316.     void *oldtrap;
  317.  
  318.     if (!the_menu) return;
  319.     calc_rects(cntrl, ctype, &title_rect, &menu_rect);
  320.     point.v=menu_rect.top;
  321.     point.h=menu_rect.left;
  322.     LocalToGlobal(&point);
  323.     if (cntrl[0]->contrlTitle[0]) InvertRect(&title_rect);
  324.     old_choice = cntrl[0]->contrlValue;
  325.     if (old_choice < 1 || old_choice > CountMItems(the_menu))
  326.         old_choice = 0;
  327.     InsertMenu(the_menu,-1);
  328.     CalcMenuSize(the_menu);
  329.     if (ctype & FIXED_MENU_WIDTH) the_menu[0]->menuWidth = menu_rect.right - menu_rect.left;
  330.     else if (ctype & NEW_STYLE)   the_menu[0]->menuWidth += EXTRA_SPACE_FOR_NEW_STYLE;
  331.     if (ctype & OVERRIDE_MENU_SIZE)
  332.         {
  333.         oldtrap = GetToolTrapAddress(_CalcMenuSize);
  334.         original_menuwidth = the_menu[0]->menuWidth;
  335.         SetToolTrapAddress((ProcPtr)CalcMenuSize_patch, _CalcMenuSize);
  336.         *menuSize = *(unsigned long *)&the_menu[0]->menuWidth;
  337.         if (TrapAvailable(_HWPriv)) FlushInstructionCache();
  338.         }
  339.     chosen = PopUpMenuSelect(the_menu, point.v, point.h, old_choice);
  340.     if (ctype & OVERRIDE_MENU_SIZE)
  341.         {
  342.         the_menu[0]->menuWidth = original_menuwidth;
  343.         SetToolTrapAddress(oldtrap, _CalcMenuSize);
  344.         }
  345.     if (cntrl[0]->contrlTitle[0]) InvertRect(&title_rect);
  346.     DeleteMenu(the_menu[0]->menuID);
  347.     if (chosen & 0xFFFF0000)    // If some item was chosen
  348.         {
  349.         retval = 1;
  350.         if ((chosen & 0xFFFF) != old_choice)    // If choice has changed
  351.             {
  352.             cntrl[0]->contrlValue = (chosen & 0xFFFF);
  353.             InsetRect(&menu_rect,-1,-1);
  354.             menu_rect.bottom += 1;
  355.             menu_rect.right  += 1;
  356.             EraseRect(&menu_rect);
  357.             if (cntrl[0]->contrlVis) draw(ctype,cntrl);
  358.             }
  359.         }
  360.     return(retval);
  361.     }
  362.  
  363. // Note: subtle problems here.
  364. // The menu resource read with GetMenu is detached because ResEdit has a tendency to
  365. // create multiple concurrent invocations of the same control (eg in the DLOG window
  366. // and in the DITL window) at the same time. If all the CNTLs share the same menu
  367. // resource in memory, then as soon as one of them is disposed, the resource gets
  368. // released and all the others crash when they try to access it. (There is no
  369. // reference counting on number of clients accessing a resource.) Consequently we
  370. // give each instance its own private copy of the menu by calling DetachResource.
  371.  
  372. pascal long main(short ctype, ControlHandle cntrl, short message, long parameter)
  373.     {
  374.     Rect title_rect, menu_rect;
  375.     long retval = 0;
  376.     PenState    save_pen;
  377.     short        save_size;
  378.     short        save_font;
  379.     Style        save_face;
  380.     
  381.     HLock((Handle)cntrl);
  382.     if (message != initCntl)
  383.         {
  384.         if (!cntrl[0]->contrlData)
  385.             {
  386.             DebugStr("\pError: Popup Not Initialized");
  387.             HUnlock((Handle)cntrl);
  388.             return(-1);
  389.             }
  390.         HLock(cntrl[0]->contrlData);
  391.         }
  392.     GetPenState(&save_pen);
  393.     save_font=cntrl[0]->contrlOwner->txFont;
  394.     save_size=cntrl[0]->contrlOwner->txSize;
  395.     save_face=cntrl[0]->contrlOwner->txFace;
  396.  
  397.     ctype &= 15;
  398.     switch(message)
  399.         {
  400.         case drawCntl : if (cntrl[0]->contrlVis) draw(ctype, cntrl);
  401.                         break;
  402.         case testCntl : calc_rects(cntrl, ctype, &title_rect, &menu_rect);
  403.                         if (cntrl[0]->contrlHilite != INACTIVE)
  404.                             if (PtInRect(*(Point*)¶meter, &menu_rect))
  405.                                 retval=1;
  406.                         // Note: used to return 131, which results in Control
  407.                         // Manager calling dragCntl instead of autoTrack, and
  408.                         // that results in trip DialogSelect not returning TRUE
  409.                         // and the item hit which results in the application
  410.                         // not knowing that the menu has been clicked on...
  411.                         break;
  412.         case calcCRgns: parameter &= 0xFFFFFF;    // Old style 24-bit call
  413.         case calcCntlRgn: case calcThumbRgn :    // New style 32-bit clean version
  414.                         calc_rects(cntrl, ctype, &title_rect, &menu_rect);
  415.                         menu_rect.top    -= 1;
  416.                         menu_rect.left   -= 1;
  417.                         menu_rect.bottom += 2;
  418.                         menu_rect.right  += 2;
  419.                         UnionRect(&title_rect, &menu_rect, &menu_rect);
  420.                         RectRgn((RgnHandle)parameter, &menu_rect); break;
  421.                         break;
  422.         case initCntl : cntrl[0]->contrlData = NewHandle(sizeof(pop_up_data));
  423.                         if (!cntrl[0]->contrlData) DebugStr("\pNo memory");
  424.                         cntrl[0]->contrlAction = (ControlActionUPP)-1;
  425.                         // The info is copied out of the contrlMin/Max fields,
  426.                         // which are then reset so that SetCtlValue calls will
  427.                         // work.
  428.                         POPDATA(cntrl)->menu   = NULL;
  429.                         POPDATA(cntrl)->menuID = 0;
  430.                         POPDATA(cntrl)->width  = cntrl[0]->contrlMax;
  431.                         POPDATA(cntrl)->resID  = cntrl[0]->contrlMin;
  432.                         POPDATA(cntrl)->font   = 0;        // Font and size no
  433.                         POPDATA(cntrl)->size   = 0;        // longer supported
  434.                         cntrl[0]->contrlMin = 0;
  435.                         cntrl[0]->contrlMax = 0x7FFF;
  436.                         if (POPDATA(cntrl)->resID)
  437.                             {
  438.                             POPDATA(cntrl)->menu   = GetMenu(POPDATA(cntrl)->resID);
  439.                             POPDATA(cntrl)->menuID = POPDATA(cntrl)->menu[0]->menuID;
  440.                             DetachResource((Handle)POPDATA(cntrl)->menu);
  441.                             if (ctype & USE_ADD_RES_MENU)
  442.                                 {
  443.                                 AddResMenu(POPDATA(cntrl)->menu, cntrl[0]->contrlRfCon);
  444.                                 //CalcMenuSize(POPDATA(cntrl)->menu);
  445.                                 }
  446.                             cntrl[0]->contrlMax = CountMItems(POPDATA(cntrl)->menu);
  447.                             }
  448.                         break;
  449.         case dispCntl : if (POPDATA(cntrl)->resID)
  450.             DisposHandle((Handle)POPDATA(cntrl)->menu);
  451.                         DisposHandle(cntrl[0]->contrlData);
  452.                         cntrl[0]->contrlData = NULL;
  453.                         break;
  454.         case posCntl  : break;
  455.         case thumbCntl: break;
  456.         case dragCntl : break;
  457.         case autoTrack: retval=drag(ctype, cntrl); break;
  458.         }
  459.     SetPenState(&save_pen);
  460.     TextFont(save_font);
  461.     TextSize(save_size);
  462.     TextFace(save_face);
  463.     if (message != dispCntl) HUnlock(cntrl[0]->contrlData);
  464.     HUnlock((Handle)cntrl);
  465.     return(retval);
  466.     }
  467.